/*
 * Copyright 2016, Data61, CSIRO (ABN 41 687 119 230)
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#ifndef _RPC_INTERFACE_DATA_CLIENT_HELPER_H_
#define _RPC_INTERFACE_DATA_CLIENT_HELPER_H_

#include <stdint.h>
#include <stdlib.h>
#include <refos/refos.h>
#include <refos/error.h>
#include <refos-rpc/rpc.h>
#include <refos-rpc/data_client.h>
#include <refos-rpc/proc_client_helper.h>
#include <refos-util/walloc.h>
#include <refos/sync.h>
#include <refos-util/cspace.h>

/*! @file
    @brief Helper functions for the data interface.

    This file contains a simple layer of helper functions that make using the dataspace interface
    much easier, but are too complex to have been generated by the stub generator. 
*/

/*! @brief Set the data_open() to paddr mode, for dataservers which support opening a dataspace at a
           specific physical address. Mainly used for device MMIO.
*/
#define DSPACE_FLAG_DEVICE_PADDR 0x10000000
#define DSPACE_FLAG_UNCACHED     0x20000000

/*! @brief Structure containing state for a mapped dataspace. */
typedef struct data_mapping {
    seL4_CPtr session; /* No ownership. */
    seL4_CPtr dataspace;  /* Has ownership. */
    seL4_CPtr window;  /* Has ownership. */

    char* vaddr;
 
    int size;
    int sizeNPages;
    int dspaceSize;

    refos_err_t err;
} data_mapping_t;

/*! @brief Helper function to open a dataspace, allocate a window to map into, then map it there.
    @param session The client connection session to the dataspace server.  (No ownership)
    @param name The name of the dataspace server.
    @param flags The read / write / create flags.
    @param mode The mode to create new file with, in the case that a new one is created.
    @param size The size of mapping window.
    @param dspaceSize The size of dataspace to open. Note that some data servers may ignore this.
    @return A data_mapping_t structure containing the result mapping. Look in the err member of the
            structure to check for any errors that have occured. (Gives ownership)
*/
static inline data_mapping_t
data_open_map(seL4_CPtr session, char* name, int flags, int mode, int size, int dspaceSize)
{
    int errnoRetVal = EINVALID;
    data_mapping_t d;
    memset(&d, 0, sizeof(data_mapping_t));
    d.session = session;

    /* Allocate window. */
    d.size = size;
    d.sizeNPages = (size / REFOS_PAGE_SIZE) + ((size % REFOS_PAGE_SIZE) ? 1 : 0);
    seL4_Word vaddr = 0;
    if (flags & DSPACE_FLAG_UNCACHED) {
        vaddr = walloc_ext(d.sizeNPages, &d.window, PROC_WINDOW_PERMISSION_READWRITE,
                           PROC_WINDOW_FLAGS_UNCACHED);
    } else {
        vaddr = walloc(d.sizeNPages, &d.window);
    }
    if (!vaddr || !d.window) {
        errnoRetVal = ENOMEM;
        goto exit0;
    }
    d.vaddr = (char*) vaddr;

    /* Open the dataspace. */
    if (dspaceSize < 0) {
        /* Default dataspace size to same size as the window. */
        dspaceSize = size;
    }
    d.dspaceSize = dspaceSize;
    d.dataspace = data_open(session, name, flags, mode, dspaceSize, &errnoRetVal);
    if (errnoRetVal != ESUCCESS || d.dataspace == 0) {
        REFOS_SET_ERRNO(errnoRetVal);
        goto exit1;
    }

    /* Map the dataspace. */
    errnoRetVal = data_datamap(session, d.dataspace, d.window, 0);
    if (errnoRetVal != ESUCCESS) {
        REFOS_SET_ERRNO(errnoRetVal);
        goto exit2;
    }

    REFOS_SET_ERRNO(ESUCCESS);
    d.err = ESUCCESS;
    return d;

    /* Exit stack. */
exit2:
    data_close(session, d.dataspace);
    seL4_CNode_Delete(REFOS_CSPACE, d.dataspace, REFOS_CDEPTH);
    csfree(d.dataspace);

exit1:
    walloc_free(vaddr, d.sizeNPages);

exit0:
    REFOS_SET_ERRNO(errnoRetVal);
    memset(&d, 0, sizeof(data_mapping_t));
    d.err = errnoRetVal;
    return d;
}

/*! @brief Release a data mapping previous initialised by @ref data_open_map. 
    @param d The previously mapped dataspace. (Takes ownership)
    @return ESUCCESS on success, refos_err_t error otherwise.
*/
static inline refos_err_t
data_mapping_release(data_mapping_t d)
{
    if (d.err != ESUCCESS) {
        return ESUCCESS;
    }

    if (d.window) {
        refos_err_t error = data_dataunmap(d.session, d.window);
        if (error != ESUCCESS) {
            return error;
        }
        assert(d.vaddr);
        walloc_free((uint32_t) d.vaddr, d.sizeNPages);
    }

    refos_err_t error = data_close(d.session, d.dataspace);
    if (error != ESUCCESS) {
        return error;
    }
    csfree_delete(d.dataspace);

    memset(&d, 0, sizeof(data_mapping_t));
    return ESUCCESS;
}

/*! @brief Helper function for data_provide_data_from_parambuffer().

    Helper function for data_provide_data_from_parambuffer(), that basically copies the given
    content into the given parameter buffer before calling data_provide_data_from_parambuffer().

    @param session The client connection session to the dataspace server.  (No ownership)
    @param dspace_fd The cap to the remote dataspace to provide the content for.
    @param offset The offset into the remote dataspace to provide content for.
    @param content The content buffer. (No ownership)
    @param contentSize The size of the content. (May have maximum content size.)
    @param paramBuffer The parameter buffer that has been set up.
    @return ESUCCESS if success, refos_error error code otherwise.
*/
static inline refos_err_t
data_provide_data(seL4_CPtr session, seL4_CPtr dspace_fd, uint32_t offset, char *content,
                  uint32_t contentSize, data_mapping_t* paramBuffer)
{
    if (!content) {
        return EINVALIDPARAM;
    }
    if (!paramBuffer || paramBuffer->err != ESUCCESS) {
        return ENOPARAMBUFFER;
    }
    if (contentSize >= paramBuffer->size) {
        return ENOMEM;
    }
    memcpy(paramBuffer->vaddr, content, contentSize);
    return data_provide_data_from_parambuffer(session, dspace_fd, offset, contentSize);
}

#endif /* _RPC_INTERFACE_DATA_CLIENT_HELPER_H_ */
